import '../file_node.dart';
import '../file_explorer_controller.dart';

/// [FileExplorerManager]：抽离自 `FileExplorer` 的核心非 UI 逻辑。
///
/// 功能职责：
/// - 管理「懒加载」逻辑（按需加载子节点）；
/// - 避免重复发起 I/O（去重机制 `_inflight`）；
/// - 维护版本号以解决竞态问题（`_loadVersion`）；
/// - 支持路径查找和节点展开时的批量加载。
///
/// 架构说明：
/// - 持有 [FileExplorerController]，用于存储加载结果、loading 状态和刷新信号；
/// - 对外暴露 `ensureExpandedNodesLoaded` / `loadChildrenForNode` / `findNodeByPath` 等核心方法；
/// - 内部 `_ensureLoadedForPath` 用于逐层解析路径并保证子节点加载。
///
/// 使用示例：
/// ```dart
/// final manager = FileExplorerManager(controller);
///
/// // 确保已展开节点的子节点全部加载
/// await manager.ensureExpandedNodesLoaded(rootNodes);
///
/// // 强制刷新某个节点
/// await manager.loadChildrenForNode(node, 'src/utils', forceReload: true);
///
/// // 根据路径查找节点
/// final node = manager.findNodeByPath(rootNodes, 'src/utils/file.dart');
/// ```
class FileExplorerManager {
  /// 文件树控制器，保存状态（展开路径、已加载子节点、loading 状态等）。
  final FileExplorerController ctl;

  /// 记录正在进行的 I/O，避免重复加载。
  final Map<String, Future<List<FileNode>>> _inflight = {};

  /// 路径对应的加载版本号，用于解决异步竞态（保证只接受最新结果）。
  final Map<String, int> _loadVersion = {};

  FileExplorerManager(this.ctl);

  /// 清理某个路径及其子路径的版本记录（折叠时调用）。
  ///
  /// - [path] 要清理的逻辑路径；
  /// - 会移除该路径及所有子路径的 `_loadVersion`。
  void clearVersionFor(String path) {
    _loadVersion.removeWhere((k, _) => k == path || k.startsWith('$path/'));
  }

  /// 遍历所有已展开的路径，确保这些路径的子节点已加载。
  ///
  /// - [roots] 根节点集合；
  /// - 会按路径深度排序，逐层调用 [_ensureLoadedForPath]；
  /// - 常用于「刷新 UI 前」确保展开节点的数据已经就绪。
  Future<void> ensureExpandedNodesLoaded(List<FileNode> roots) async {
    final paths = ctl.expanded.value.toList()
      ..sort((a, b) => a.split('/').length.compareTo(b.split('/').length));

    for (final p in paths) {
      await _ensureLoadedForPath(roots, p);
    }
  }

  /// 确保指定路径的节点已加载。
  ///
  /// 内部逻辑：
  /// - 从 [roots] 开始逐层遍历路径；
  /// - 若遇到可懒加载的文件夹且尚未加载，则调用 [loadChildrenForNode]；
  /// - 否则使用已有的 [ctl.loadedChildren] 或节点自带的 `children`。
  Future<void> _ensureLoadedForPath(List<FileNode> roots, String path) async {
    List<FileNode> currentList = roots;
    FileNode? current;
    String acc = '';

    final parts = path.split('/');
    for (final part in parts) {
      current = _firstWhereOrNull(currentList, (n) => n.name == part);
      if (current == null) return;

      acc = acc.isEmpty ? part : '$acc/$part';

      if (current.isFolder) {
        if (ctl.loadedChildren.containsKey(acc)) {
          currentList = ctl.loadedChildren[acc]!;
        } else if (current.canLazyLoad) {
          await loadChildrenForNode(current, acc);
          currentList = ctl.loadedChildren[acc] ?? const <FileNode>[];
        } else {
          currentList = current.children;
        }
      } else {
        return;
      }
    }
  }

  /// 主加载函数：支持懒加载、强制刷新、I/O 去重和版本竞态保护。
  ///
  /// - [node] 目标节点；
  /// - [path] 节点逻辑路径；
  /// - [forceReload] 是否强制重新加载（忽略缓存）；
  ///
  /// 逻辑步骤：
  /// 1. 若节点不可懒加载，或已加载过且非强制刷新，则直接返回；
  /// 2. 若已有正在进行的加载，则等待其完成（I/O 去重）；
  /// 3. 标记 loading 状态，并增加路径版本号；
  /// 4. 调用 [FileNode.loader] 执行加载；
  /// 5. 若仍是最新版本，则更新 [ctl.loadedChildren] 并递增 `childrenRevision`；
  /// 6. 清理 loading 状态和 `_inflight`。
  Future<void> loadChildrenForNode(
      FileNode node,
      String path, {
        bool forceReload = false,
      }) async {
    if (!node.canLazyLoad ||
        (!forceReload && ctl.loadedChildren.containsKey(path))) {
      return;
    }

    if (_inflight.containsKey(path)) {
      await _inflight[path];
      return;
    }

    ctl.setLoading(path, true);
    final v = (_loadVersion[path] ?? 0) + 1;
    _loadVersion[path] = v;

    final future = node.loader!.call().catchError((_) => <FileNode>[]);
    _inflight[path] = future;

    final children = await future;

    final stillLatest = _loadVersion[path] == v;
    if (stillLatest) {
      ctl.loadedChildren[path] = children;
      ctl.childrenRevision.value++; // 通知 UI
    }
    ctl.setLoading(path, false);
    _inflight.remove(path);
  }

  /// 根据路径（例如 `"a/b/c"`) 从 [roots] 中查找对应节点。
  ///
  /// - [roots] 根节点集合；
  /// - [path] 逻辑路径（以 `/` 分隔）；
  /// - 找到则返回 [FileNode]，否则返回 `null`。
  FileNode? findNodeByPath(List<FileNode> roots, String path) {
    List<FileNode> list = roots;
    FileNode? current;
    final parts = path.split('/');

    for (var i = 0; i < parts.length; i++) {
      final part = parts[i];
      current = _firstWhereOrNull(list, (n) => n.name == part);
      if (current == null) return null;
      if (i == parts.length - 1) return current;
      list = current.children;
    }
    return current;
  }

  // ———————————————————— 工具方法 ———————————————————— //

  /// 返回第一个匹配 [test] 的元素，若没有则返回 `null`。
  T? _firstWhereOrNull<T>(Iterable<T> items, bool Function(T) test) {
    for (final item in items) {
      if (test(item)) return item;
    }
    return null;
  }
}
